jest.autoMockOff();
jest.mock('http');
jest.mock('https');
jest.mock('../ParseACL');

const ParseError = require('../ParseError').default;
const ParseFile = require('../ParseFile').default;
const b64Digit = require('../ParseFile').b64Digit;

const ParseObject = require('../ParseObject').default;
const CoreManager = require('../CoreManager').default;
const mockFetch = require('./test_helpers/mockFetch');

const mockLocalDatastore = {
  _updateLocalIdForObject: jest.fn((_localId, /** @type {ParseObject}*/ object) => {
    if (!mockLocalDatastore.isEnabled) {
      return;
    }
    // (Taken from LocalDataStore source) This fails for nested objects that are not ParseObject
    /* eslint-disable @typescript-eslint/no-unused-vars */
    const objectKey = mockLocalDatastore.getKeyForObject(object);
  }),
  _updateObjectIfPinned: jest.fn(),
  getKeyForObject: jest.fn(object => {
    // (Taken from LocalDataStore source) This fails for nested objects that are not ParseObject
    const OBJECT_PREFIX = 'Parse_LDS_';
    const objectId = object.objectId || object._getId();
    return `${OBJECT_PREFIX}${object.className}_${objectId}`;
  }),
};
jest.setMock('../LocalDatastore', mockLocalDatastore);

function generateSaveMock(prefix) {
  return function (name, payload, options) {
    if (options && typeof options.progress === 'function') {
      options.progress(0.5, 5, 10, { type: 'upload' });
    }
    return Promise.resolve({
      name: name,
      url: prefix + name,
    });
  };
}

const defaultController = CoreManager.getFileController();

describe('ParseFile', () => {
  beforeEach(() => {
    ParseObject.enableSingleInstance();
    jest.clearAllMocks();
    CoreManager.setFileController({
      saveFile: generateSaveMock('http://files.parsetfss.com/a/'),
      saveBase64: generateSaveMock('http://files.parsetfss.com/a/'),
      download: () =>
        Promise.resolve({
          base64: 'ParseA==',
          contentType: 'image/png',
        }),
    });
  });

  afterEach(() => {
    process.env.PARSE_BUILD = 'node';
  });

  it('can create files with base64 encoding (no padding)', () => {
    const file = new ParseFile('parse.txt', { base64: 'YWJj' });
    expect(file._source.base64).toBe('YWJj');
    expect(file._source.type).toBe('text/plain');
    expect(file._data).toBe('YWJj');
  });

  it('can create files with base64 encoding (1 padding)', () => {
    const file = new ParseFile('parse.txt', { base64: 'YWI=' });
    expect(file._source.base64).toBe('YWI=');
    expect(file._source.type).toBe('text/plain');
    expect(file._data).toBe('YWI=');
  });

  it('can create files with base64 encoding (2 padding)', () => {
    const file = new ParseFile('parse.txt', { base64: 'ParseA==' });
    expect(file._source.base64).toBe('ParseA==');
    expect(file._source.type).toBe('text/plain');
    expect(file._data).toBe('ParseA==');
  });

  it('can set the default type to be text/plain when using base64', () => {
    const file = new ParseFile('parse.txt', {
      base64: 'data:;base64,ParseA==',
    });
    expect(file._source.base64).toBe('ParseA==');
    expect(file._source.type).toBe('text/plain');
    expect(file._data).toBe('ParseA==');
  });

  it('can extract data type from base64', () => {
    const file = new ParseFile('parse.png', {
      base64: '',
    });
    expect(file._source.base64).toBe('ParseA==');
    expect(file._source.type).toBe('image/png');
    expect(file._data).toBe('ParseA==');
  });

  it('can extract data type from base64 with a filename parameter', () => {
    const file = new ParseFile('parse.pdf', {
      base64: 'data:application/pdf;filename=parse.pdf;base64,ParseA==',
    });
    expect(file._source.base64).toBe('ParseA==');
    expect(file._source.type).toBe('application/pdf');
    expect(file._data).toBe('ParseA==');
  });

  it('can create files with file uri', () => {
    const file = new ParseFile('parse-image', {
      uri: 'http://example.com/image.png',
    });
    expect(file._source.format).toBe('uri');
    expect(file._source.uri).toBe('http://example.com/image.png');
  });

  it('can extract data type from base64 with data type containing a number', () => {
    const file = new ParseFile('parse.m4a', {
      base64: 'data:audio/m4a;base64,ParseA==',
    });
    expect(file._source.base64).toBe('ParseA==');
    expect(file._source.type).toBe('audio/m4a');
    expect(file._data).toBe('ParseA==');
  });

  it('can extract data type from base64 with a complex mime type', () => {
    const file = new ParseFile('parse.kml', {
      base64: 'data:application/vnd.google-earth.kml+xml;base64,ParseA==',
    });
    expect(file._source.base64).toBe('ParseA==');
    expect(file._source.type).toBe('application/vnd.google-earth.kml+xml');
    expect(file._data).toBe('ParseA==');
  });

  it('can extract data type from base64 with a charset param', () => {
    const file = new ParseFile('parse.kml', {
      base64: 'data:application/vnd.3gpp.pic-bw-var;charset=utf-8;base64,ParseA==',
    });
    expect(file._source.base64).toBe('ParseA==');
    expect(file._source.type).toBe('application/vnd.3gpp.pic-bw-var');
    expect(file._data).toBe('ParseA==');
  });

  it('can create files with byte arrays', () => {
    const file = new ParseFile('parse.txt', [61, 170, 236, 120]);
    expect(file._source.base64).toBe('ParseA==');
    expect(file._source.type).toBe('');
    expect(file._data).toBe('ParseA==');
  });

  it('can create files with  Uint8Arrays', () => {
    const file = new ParseFile('parse.txt', new Uint8Array([61, 170, 236, 120]));
    expect(file._source.base64).toBe('ParseA==');
    expect(file._source.type).toBe('');
    expect(file._data).toBe('ParseA==');
  });

  it('can create files with all types of characters', () => {
    const file = new ParseFile('parse.txt', [11, 239, 191, 215, 80, 52]);
    expect(file._source.base64).toBe('C++/11A0');
    expect(file._source.type).toBe('');
    expect(file._data).toBe('C++/11A0');
  });

  it('can create an empty file', () => {
    const file = new ParseFile('parse.txt');
    expect(file.name()).toBe('parse.txt');
    expect(file.url()).toBe(undefined);
  });

  it('throws when creating a file with invalid data', () => {
    expect(function () {
      new ParseFile('parse.txt', 12);
    }).toThrow('Cannot create a Parse.File with that data.');

    expect(function () {
      new ParseFile('parse.txt', null);
    }).toThrow('Cannot create a Parse.File with that data.');

    expect(function () {
      new ParseFile('parse.txt', 'string');
    }).toThrow('Cannot create a Parse.File with that data.');
  });

  it('throws with invalid base64', () => {
    expect(function () {
      b64Digit(65);
    }).toThrow('Tried to encode large digit 65 in base64.');
  });

  it('returns secure url when specified', () => {
    const file = new ParseFile('parse.txt', { base64: 'ParseA==' });
    return file.save().then(function (result) {
      expect(result).toBe(file);
      expect(result.url({ forceSecure: true })).toBe('https://files.parsetfss.com/a/parse.txt');
    });
  });

  it('returns undefined when there is no url', () => {
    const file = new ParseFile('parse.txt', { base64: 'ParseA==' });
    expect(file.url({ forceSecure: true })).toBeUndefined();
  });

  it('updates fields when saved', () => {
    const file = new ParseFile('parse.txt', { base64: 'ParseA==' });
    expect(file.name()).toBe('parse.txt');
    expect(file.url()).toBe(undefined);
    return file.save().then(function (result) {
      expect(result).toBe(file);
      expect(result.name()).toBe('parse.txt');
      expect(result.url()).toBe('http://files.parsetfss.com/a/parse.txt');
    });
  });

  it('updates fields when saved with uri', () => {
    const file = new ParseFile('parse.png', {
      uri: 'https://example.com/image.png',
    });
    expect(file.name()).toBe('parse.png');
    expect(file.url()).toBe(undefined);
    return file.save().then(function (result) {
      expect(result).toBe(file);
      expect(result.name()).toBe('parse.png');
      expect(result.url()).toBe('http://files.parsetfss.com/a/parse.png');
    });
  });

  it('generates a JSON representation', () => {
    const file = new ParseFile('parse.txt', { base64: 'ParseA==' });
    return file.save().then(function (result) {
      expect(result.toJSON()).toEqual({
        __type: 'File',
        name: 'parse.txt',
        url: 'http://files.parsetfss.com/a/parse.txt',
      });
    });
  });

  it('can construct a file from a JSON object', () => {
    const f = ParseFile.fromJSON({
      __type: 'File',
      name: 'parse.txt',
      url: 'http://files.parsetfss.com/a/parse.txt',
    });
    expect(f).toBeTruthy();
    expect(f.name()).toBe('parse.txt');
    expect(f.url()).toBe('http://files.parsetfss.com/a/parse.txt');

    expect(ParseFile.fromJSON.bind(null, {})).toThrow('JSON object does not represent a ParseFile');
  });

  it('can test equality against another ParseFile', () => {
    let a = new ParseFile('parse.txt', [61, 170, 236, 120]);
    let b = new ParseFile('parse.txt', [61, 170, 236, 120]);

    expect(a.equals(a)).toBe(true);
    // unsaved files are never equal
    expect(a.equals(b)).toBe(false);
    expect(b.equals(a)).toBe(false);

    a = ParseFile.fromJSON({
      __type: 'File',
      name: 'parse.txt',
      url: 'http://files.parsetfss.com/a/parse.txt',
    });
    b = ParseFile.fromJSON({
      __type: 'File',
      name: 'parse.txt',
      url: 'http://files.parsetfss.com/a/parse.txt',
    });

    expect(a.equals(b)).toBe(true);
    expect(b.equals(a)).toBe(true);

    b = ParseFile.fromJSON({
      __type: 'File',
      name: 'parse.txt',
      url: 'http://files.parsetfss.com/b/parse.txt',
    });

    expect(a.equals(b)).toBe(false);
    expect(b.equals(a)).toBe(false);
  });

  it('reports progress during save when source is a File', () => {
    const file = new ParseFile('progress.txt', new File(['Parse'], 'progress.txt'));

    const options = {
      progress: function () {},
    };
    jest.spyOn(options, 'progress');

    return file.save(options).then(function (f) {
      expect(options.progress).toHaveBeenCalledWith(0.5, 5, 10, {
        type: 'upload',
      });
      expect(f).toBe(file);
      expect(f.name()).toBe('progress.txt');
      expect(f.url()).toBe('http://files.parsetfss.com/a/progress.txt');
    });
  });

  it('can cancel file upload', () => {
    const mockRequestTask = {
      abort: () => {},
    };
    CoreManager.setFileController({
      saveFile: function (name, payload, options) {
        options.requestTask(mockRequestTask);
        return Promise.resolve({});
      },
      saveBase64: () => {},
      download: () => {},
    });
    const file = new ParseFile('progress.txt', new File(['Parse'], 'progress.txt'));

    jest.spyOn(mockRequestTask, 'abort');
    file.cancel();
    expect(mockRequestTask.abort).toHaveBeenCalledTimes(0);

    file.save();

    expect(file._requestTask).toEqual(mockRequestTask);
    file.cancel();
    expect(mockRequestTask.abort).toHaveBeenCalledTimes(1);
  });

  it('should save file with metadata and tag options', async () => {
    const fileController = {
      saveFile: jest.fn().mockResolvedValue({}),
      saveBase64: () => {},
      download: () => {},
    };
    CoreManager.setFileController(fileController);
    const file = new ParseFile('donald_duck.txt', new File(['Parse'], 'donald_duck.txt'));
    file.addMetadata('foo', 'bar');
    file.addTag('bar', 'foo');
    await file.save();
    expect(fileController.saveFile).toHaveBeenCalledWith(
      'donald_duck.txt',
      {
        file: expect.any(File),
        format: 'file',
        type: '',
      },
      {
        metadata: { foo: 'bar' },
        tags: { bar: 'foo' },
        requestTask: expect.any(Function),
      }
    );
  });

  it('should create new ParseFile with metadata and tags', () => {
    const metadata = { foo: 'bar' };
    const tags = { bar: 'foo' };
    const file = new ParseFile('parse.txt', [61, 170, 236, 120], '', metadata, tags);
    expect(file._source.base64).toBe('ParseA==');
    expect(file._source.type).toBe('');
    expect(file._data).toBe('ParseA==');
    expect(file.metadata()).toBe(metadata);
    expect(file.tags()).toBe(tags);
  });

  it('should set metadata', () => {
    const file = new ParseFile('parse.txt', [61, 170, 236, 120]);
    file.setMetadata({ foo: 'bar' });
    expect(file.metadata()).toEqual({ foo: 'bar' });
  });

  it('should set metadata key', () => {
    const file = new ParseFile('parse.txt', [61, 170, 236, 120]);
    file.addMetadata('foo', 'bar');
    expect(file.metadata()).toEqual({ foo: 'bar' });
  });

  it('should not set metadata if key is not a string', () => {
    const file = new ParseFile('parse.txt', [61, 170, 236, 120]);
    file.addMetadata(10, '');
    expect(file.metadata()).toEqual({});
  });

  it('should set tags', () => {
    const file = new ParseFile('parse.txt', [61, 170, 236, 120]);
    file.setTags({ foo: 'bar' });
    expect(file.tags()).toEqual({ foo: 'bar' });
  });

  it('should set tag key', () => {
    const file = new ParseFile('parse.txt', [61, 170, 236, 120]);
    file.addTag('foo', 'bar');
    expect(file.tags()).toEqual({ foo: 'bar' });
  });

  it('should not set tag if key is not a string', () => {
    const file = new ParseFile('parse.txt', [61, 170, 236, 120]);
    file.addTag(10, 'bar');
    expect(file.tags()).toEqual({});
  });
});

describe('FileController', () => {
  beforeEach(() => {
    CoreManager.setFileController(defaultController);
    const request = function (method, path) {
      const name = path.substr(path.indexOf('/') + 1);
      return Promise.resolve({
        name: name,
        url: 'https://files.parsetfss.com/a/' + name,
      });
    };
    const ajax = function (method, path) {
      const name = path.substr(path.indexOf('/') + 1);
      return Promise.resolve({
        response: {
          name: name,
          url: 'https://files.parsetfss.com/a/' + name,
        },
      });
    };
    CoreManager.setRESTController({ request: request, ajax: ajax });
  });

  it('saves files created with bytes', () => {
    const file = new ParseFile('parse.txt', [61, 170, 236, 120]);
    return file.save().then(function (f) {
      expect(f).toBe(file);
      expect(f.name()).toBe('parse.txt');
      expect(f.url()).toBe('https://files.parsetfss.com/a/parse.txt');
    });
  });

  it('saves files via ajax', () => {
    const blob = new Blob([61, 170, 236, 120]);
    const file = new ParseFile('parse.txt', blob);
    file._source.format = 'file';

    return file.save().then(function (f) {
      expect(f).toBe(file);
      expect(f.name()).toBe('parse.txt');
      expect(f.url()).toBe('https://files.parsetfss.com/a/parse.txt');
    });
  });

  it('saveUri with uri type', async () => {
    const file = new ParseFile('parse.png', {
      uri: 'https://example.com/image.png',
    });
    const spy = jest.spyOn(defaultController, 'download').mockImplementationOnce(() => {
      return Promise.resolve({
        base64: 'ParseA==',
        contentType: 'image/png',
      });
    });

    const spy2 = jest.spyOn(defaultController, 'saveBase64');
    await file.save();
    expect(defaultController.download).toHaveBeenCalledTimes(1);
    expect(defaultController.saveBase64).toHaveBeenCalledTimes(1);
    expect(defaultController.saveBase64.mock.calls[0][0]).toEqual('parse.png');
    expect(defaultController.saveBase64.mock.calls[0][1]).toEqual({
      format: 'base64',
      base64: 'ParseA==',
      type: 'image/png',
    });
    spy.mockRestore();
    spy2.mockRestore();
  });

  it('save with uri download abort', async () => {
    const file = new ParseFile('parse.png', {
      uri: 'https://example.com/image.png',
    });
    const spy = jest.spyOn(defaultController, 'download').mockImplementationOnce(() => {
      return Promise.resolve({});
    });

    const spy2 = jest.spyOn(defaultController, 'saveBase64');
    await file.save();
    expect(defaultController.download).toHaveBeenCalledTimes(1);
    expect(defaultController.saveBase64).toHaveBeenCalledTimes(0);
    spy.mockRestore();
    spy2.mockRestore();
  });

  it('download with ajax', async () => {
    const response = 'hello';
    mockFetch([{ status: 200, response }], { 'Content-Length': 64, 'Content-Type': 'image/png' });
    const options = {
      requestTask: () => {},
    };
    const data = await defaultController.download('https://example.com/image.png', options);
    expect(data.base64).toBeDefined();
    expect(data.contentType).toBe('image/png');
  });

  it('download with ajax no response', async () => {
    mockFetch([{ status: 200, response: {} }], { 'Content-Length': 0 });
    const options = {
      requestTask: () => {},
    };
    const data = await defaultController.download('https://example.com/image.png', options);
    expect(data).toEqual({
      base64: '',
      contentType: undefined,
    });
  });

  it('download with ajax abort', async () => {
    mockFetch([], {}, { name: 'AbortError' });
    let _requestTask;
    const options = {
      requestTask: task => (_requestTask = task),
    };
    defaultController.download('https://example.com/image.png', options).then(data => {
      expect(data).toEqual({});
    });
    expect(_requestTask).toBeDefined();
    expect(_requestTask.abort).toBeDefined();
    _requestTask.abort();
  });

  it('download with ajax error', async () => {
    mockFetch([], {}, new Error('error thrown'));
    const options = {
      requestTask: () => {},
    };
    try {
      await defaultController.download('https://example.com/image.png', options);
    } catch (e) {
      expect(e.message).toBe('error thrown');
    }
  });

  it('getData', async () => {
    const file = new ParseFile('parse.png', [61, 170, 236, 120]);
    const data = await file.getData();
    expect(data).toBe('ParseA==');
  });

  it('getData unsaved file', async () => {
    const file = new ParseFile('parse.png');
    try {
      await file.getData();
    } catch (e) {
      expect(e.message).toBe('Cannot retrieve data for unsaved ParseFile.');
    }
  });

  it('getData via download', async () => {
    const file = new ParseFile('parse.txt', { base64: 'ParseA==' });
    file._data = null;
    const result = await file.save();

    const spy = jest.spyOn(defaultController, 'download').mockImplementationOnce((uri, options) => {
      options.requestTask(null);
      return Promise.resolve({
        base64: 'ParseA==',
        contentType: 'image/png',
      });
    });

    const data = await result.getData();
    expect(defaultController.download).toHaveBeenCalledTimes(1);
    expect(data).toBe('ParseA==');
    spy.mockRestore();
  });

  it('saves files via ajax with sessionToken option', () => {
    const request = function (method, path) {
      const name = path.substr(path.indexOf('/') + 1);
      return Promise.resolve({
        name: name,
        url: 'https://files.parsetfss.com/a/' + name,
      });
    };
    const ajax = function (method, path, data, headers) {
      expect(headers['X-Parse-Session-Token']).toBe('testing_sessionToken');
      const name = path.substr(path.indexOf('/') + 1);
      return Promise.resolve({
        response: {
          name: name,
          url: 'https://files.parsetfss.com/a/' + name,
        },
      });
    };
    CoreManager.setRESTController({ request, ajax });

    const blob = new Blob([61, 170, 236, 120]);
    const file = new ParseFile('parse.txt', blob);
    file._source.format = 'file';

    return file.save({ sessionToken: 'testing_sessionToken' }).then(function (f) {
      expect(f).toBe(file);
      expect(f.name()).toBe('parse.txt');
      expect(f.url()).toBe('https://files.parsetfss.com/a/parse.txt');
    });
  });

  it('saves files via ajax currentUser sessionToken', () => {
    CoreManager.set('UserController', {
      currentUserAsync() {
        return Promise.resolve({
          getSessionToken() {
            return 'currentUserToken';
          },
        });
      },
    });
    const request = function (method, path) {
      const name = path.substr(path.indexOf('/') + 1);
      return Promise.resolve({
        name: name,
        url: 'https://files.parsetfss.com/a/' + name,
      });
    };
    const ajax = function (method, path, data, headers) {
      expect(headers['X-Parse-Session-Token']).toBe('currentUserToken');
      const name = path.substr(path.indexOf('/') + 1);
      return Promise.resolve({
        response: {
          name: name,
          url: 'https://files.parsetfss.com/a/' + name,
        },
      });
    };
    CoreManager.setRESTController({ request, ajax });

    const blob = new Blob([61, 170, 236, 120]);
    const file = new ParseFile('parse.txt', blob);
    file._source.format = 'file';

    return file.save().then(function (f) {
      expect(f).toBe(file);
      expect(f.name()).toBe('parse.txt');
      expect(f.url()).toBe('https://files.parsetfss.com/a/parse.txt');
    });
  });

  it('should save file using saveFile with metadata and tags', async () => {
    CoreManager.set('UserController', {
      currentUserAsync() {
        return Promise.resolve({
          getSessionToken() {
            return 'currentUserToken';
          },
        });
      },
    });
    const request = jest.fn((method, path) => {
      const name = path.substr(path.indexOf('/') + 1);
      return Promise.resolve({
        name: name,
        url: 'https://files.parsetfss.com/a/' + name,
      });
    });
    const ajax = function (method, path, data, headers, options) {
      expect(options.sessionToken).toBe('currentUserToken');
      const name = path.substr(path.indexOf('/') + 1);
      return Promise.resolve({
        response: {
          name: name,
          url: 'https://files.parsetfss.com/a/' + name,
        },
      });
    };
    CoreManager.setRESTController({ request, ajax });

    const blob = new Blob([61, 170, 236, 120]);
    const file = new ParseFile('parse.txt', blob);
    file._source.format = 'file';
    file.addMetadata('foo', 'bar');
    file.addTag('bar', 'foo');
    const f = await file.save();
    expect(f).toBe(file);
    expect(f.name()).toBe('parse.txt');
    expect(f.url()).toBe('https://files.parsetfss.com/a/parse.txt');
    expect(request).toHaveBeenCalledWith(
      'POST',
      'files/parse.txt',
      {
        base64: 'NjExNzAyMzYxMjA=',
        fileData: {
          metadata: {
            foo: 'bar',
          },
          tags: {
            bar: 'foo',
          },
        },
      },
      { requestTask: expect.any(Function) }
    );
  });

  it('saves files via object saveAll options', async () => {
    const ajax = async () => {};
    const request = jest.fn(async (method, path, data, options) => {
      if (path.indexOf('files/') === 0) {
        expect(options.sessionToken).toBe('testToken');
        return {
          name: 'parse.txt',
          url: 'http://files.parsetfss.com/a/parse.txt',
        };
      }
      return [{ success: { objectId: 'child' } }];
    });
    CoreManager.setRESTController({ ajax, request });
    CoreManager.setLocalDatastore(mockLocalDatastore);

    const blob = new Blob([61, 170, 236, 120]);
    const file = new ParseFile('parse.txt', blob);
    file._source.format = 'file';
    const object = ParseObject.fromJSON({ className: 'TestObject' });
    object.set('file', file);
    await ParseObject.saveAll([object], { sessionToken: 'testToken' });
    expect(request).toHaveBeenCalled();
  });

  it('should throw error if file deleted without name', async () => {
    expect.assertions(1);
    const file = new ParseFile('', [1, 2, 3]);
    try {
      await file.destroy();
    } catch (e) {
      expect(e.code).toBe(ParseError.FILE_DELETE_UNNAMED_ERROR);
    }
  });

  it('should delete file with masterKey', async () => {
    const file = new ParseFile('filename', [1, 2, 3]);
    const ajax = jest.fn().mockResolvedValueOnce({ foo: 'bar' });
    CoreManager.setRESTController({ ajax, request: () => {} });
    CoreManager.set('MASTER_KEY', 'masterKey');
    const result = await file.destroy({ useMasterKey: true });
    expect(result).toEqual(file);
    expect(ajax).toHaveBeenCalledWith('DELETE', 'https://api.parse.com/1/files/filename', '', {
      'X-Parse-Application-ID': null,
      'X-Parse-Master-Key': 'masterKey',
    });
    CoreManager.set('MASTER_KEY', null);
  });

  it('should delete file', async () => {
    const file = new ParseFile('filename', [1, 2, 3]);
    const ajax = jest.fn().mockResolvedValueOnce({ foo: 'bar' });
    CoreManager.setRESTController({ ajax, request: () => {} });
    CoreManager.set('MASTER_KEY', 'masterKey');
    const result = await file.destroy({ useMasterKey: false });
    expect(result).toEqual(file);
    expect(ajax).toHaveBeenCalledWith('DELETE', 'https://api.parse.com/1/files/filename', '', {
      'X-Parse-Application-ID': null,
    });
    CoreManager.set('MASTER_KEY', null);
  });

  it('should handle delete file error', async () => {
    const file = new ParseFile('filename', [1, 2, 3]);
    const ajax = jest
      .fn()
      .mockResolvedValueOnce(Promise.reject(new ParseError(403, 'Cannot delete file.')));
    const handleError = jest.fn();
    CoreManager.setRESTController({ ajax, request: () => {}, handleError });
    const result = await file.destroy();
    expect(result).toEqual(file);
    expect(ajax).toHaveBeenCalledWith('DELETE', 'https://api.parse.com/1/files/filename', '', {
      'X-Parse-Application-ID': null,
      'X-Parse-Master-Key': null,
    });
    expect(handleError).toHaveBeenCalled();
  });

  it('should handle delete file error invalid server response', async () => {
    const file = new ParseFile('filename', [1, 2, 3]);
    const response = null;
    const ajax = jest.fn().mockResolvedValueOnce(Promise.reject(response));
    const handleError = jest.fn();
    CoreManager.setRESTController({ ajax, request: () => {}, handleError });
    const result = await file.destroy();
    expect(result).toEqual(file);
    expect(ajax).toHaveBeenCalledWith('DELETE', 'https://api.parse.com/1/files/filename', '', {
      'X-Parse-Application-ID': null,
      'X-Parse-Master-Key': null,
    });
    expect(handleError).not.toHaveBeenCalled();
  });

  it('controller saveFile format errors', async () => {
    try {
      await defaultController.saveFile('parse.txt', { format: 'base64' });
      expect(true).toBe(false);
    } catch (e) {
      expect(e.message).toBe('saveFile can only be used with File-type sources.');
    }
  });

  it('controller saveBase64 format errors', async () => {
    try {
      await defaultController.saveBase64('parse.txt', { format: 'file' });
      expect(true).toBe(false);
    } catch (e) {
      expect(e.message).toBe('saveBase64 can only be used with Base64-type sources.');
    }
  });

  it('controller saveFile file reader errors', async () => {
    const fileReader = global.FileReader;
    class FileReader {
      readAsDataURL() {
        this.onerror('Could not load file.');
      }
    }
    global.FileReader = FileReader;
    try {
      await defaultController.saveFile('parse.txt', { format: 'file' });
      expect(true).toBe(false);
    } catch (e) {
      expect(e).toBe('Could not load file.');
    }
    global.FileReader = fileReader;
  });

  it('can save unsaved Parse.File property when localDataStore is enabled.', async () => {
    mockLocalDatastore.isEnabled = true;
    const obj = new ParseObject('Item');
    const aFile = new ParseFile('myFileName', [0, 0, 0, 0, 2, 3, 4, 5]);
    obj.set('myName', 'helloworld');
    obj.set('myFile', aFile);
    let error = undefined;
    try {
      await obj.save();
    } catch (e) {
      error = e;
    }
    expect(error).toBeUndefined();
    expect(obj.get('myFile').name()).toBe('myFileName');
  });
});
